/*
  Copyright 1996-2003
  Simon Whiteside

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  $Id: TreeNodeReader.java,v 1.7 2003/02/24 18:10:53 simkin_cvs Exp $
*/

package simkin;
import java.io.*;
import java.util.*;

/**
 * This class is a parser for TreeNode text and binary files
 */
final public class TreeNodeReader
{
    static final int L_IDENT=1;
    static final int L_TEXT=2;
    static final int L_LBRACE=3;
    static final int L_RBRACE=4;
    static final int L_EOF=5;
    static final int L_ERROR=6;

    StringBuffer m_LexText;
    boolean m_UnLex;
    int m_LastLexeme;
    int m_LineNum;
    InputStream m_In=null;
    Reader m_Reader=null;
    boolean m_Peeked;
    char m_Peek;

    /**
     * Constructor - is passed an input stream
     */
    public TreeNodeReader(InputStream in){
	m_In=in;
    }
    /**
     * Constructor - is passed a reader
     */
    public TreeNodeReader(Reader in){
	m_Reader=in;
    }
    /**
     * this function reads the next character from the input stream
     */
    int readBinary() throws java.io.IOException{
	int i=readChar();
	i+=readChar()*0x100;
	return i;
    }
    int readChar() throws java.io.IOException{
	int i;
	if (m_In!=null)
	    i=m_In.read();
	else
	    i=m_Reader.read();
	return i;
    }
    /**
     * this function invokes the parser and returns a TreeNode representing the contents of the stream. The function will throw an IOException if there was an error in this process.
     */
    public TreeNode read() throws java.io.IOException{
	m_UnLex=false;
	m_LineNum=0;
	m_Peeked=false;
	int c=readChar();
	if (c==TreeNode.g_FileMagic){
	    int version=readChar();
	    Hashtable stringTable=new Hashtable();
	    return parseTreeNode(null,stringTable);
	}else{
	    m_Peek=(char)c;
	    m_Peeked=true;
	    return parseTreeNode(null);
	}
    }
    void read(byte[] buffer,int start,int length) throws java.io.IOException{
	if (m_In!=null)
	    m_In.read(buffer,start,length);
	else{
	    //	    m_Reader.read(buffer,start,length);// doesn't work!
	}
    }
    /**
     * This method parses a TreeNode from a binary stream
     */
    TreeNode parseTreeNode(TreeNode parent,Hashtable stringTable) throws java.io.IOException{
	TreeNode node=new TreeNode();
	if (parent!=null)
	    parent.addChild(node);
	int length=readBinary();
	if (length>0){
	    byte[] buffer=new byte[length];
	    read(buffer,0,length);
	    String label=new String(buffer);
	    node.label(label);
	    stringTable.put(new Integer(stringTable.size()),label);
	}else{
	    int index=readBinary();
	    if (index>0){
		String label=(String)(stringTable.get(new Integer(index-1)));
		node.label(label);
	    }
	}
	length=readBinary();
	if (length>0){
	    byte[] buffer=new byte[length];
	    read(buffer,0,length);
	    node.data(new String(buffer));
	}
	int numChildren=readBinary();
	for (int i=0;i<numChildren;i++)
	    parseTreeNode(node,stringTable);
	return node;
    }
    /**
     * this method sets the unlex flag
     */
    void unLex(){
	m_UnLex=true;
    }
    /**
     * this method causes an IOException to be thrown
     */
    void error(String msg) throws java.io.IOException{
	throw new java.io.IOException(msg+" at line "+m_LineNum+" near "+m_LexText);
    }
    /**
     * this method returns the next character in the stream, without consuming it
     */
    char peekNext() throws java.io.IOException{
	m_Peeked=true;
	int c=readChar();
	if (c==-1)
	    error("End of stream");
	m_Peek=(char)(c);
	return m_Peek;
    }
    /**
     * this method returns the next character in the stream, and consumes it
     */
    char getNext() throws java.io.IOException{
	if (m_Peeked){
	    m_Peeked=false;
	    return m_Peek;
	}else{
	    int c=readChar();
	    if (c==-1)
		error("End of stream");
	    return (char)(c);
	}
    }
    /**
     * this method adds the given character to the current token buffer
     */
    void addToLexText(char c){
	m_LexText.append(c);
    }
    /**
     * this method is called repeatedly to split the stream up into tokens
     */
    int lex() throws java.io.IOException{
	if (m_UnLex)
	    m_UnLex=false;
	else{
	    m_LexText=new StringBuffer();
	    char c;
	    boolean loop=true;
	    while (loop){
		c=getNext();
		if (c=='\n')
		    m_LineNum++;
		switch (c){
		case '{':
		    addToLexText(c);
		    m_LastLexeme=L_LBRACE;
		    loop=false;
		    break;
		case '}':
		    addToLexText(c);
		    m_LastLexeme=L_RBRACE;
		    loop=false;
		    break;
		case '[':{
		    boolean textloop=true;
		    int num_braces=1;
		    m_LastLexeme=L_TEXT;
		    while(textloop){
			c=getNext();
			switch(c){
			case '\\':
			    // let [ through
			    c=getNext();
			    if ((c=='[' || c==']')==false)
				addToLexText('\\');
			    addToLexText(c);
			    break;
			case '[':
			    num_braces++;
			    addToLexText(c);
			    break;
			case ']':
			    num_braces--;
			    if (num_braces==0)
				textloop=false;
			    else
				addToLexText(c);
			    break;
			default:
			    addToLexText(c);
			}
		    }
		    loop=false;
		    break;	
		}
		case '/':
		case '.':
		case '\\':
		case '~':
		case '_':
		case '-':
		case ':':
		default:
		    if (c==':' || c=='-' || c=='.' || c=='/' || c=='\\' || c=='_' || c=='~' || Character.isLetterOrDigit(c)){
			m_LastLexeme=L_IDENT;
			addToLexText(c);
			boolean identloop=true;
			while (identloop){
			    char peek=peekNext();
			    if (Character.isLetterOrDigit(peek) || peek=='\\' 
				|| peek=='_' || peek=='~' || peek=='-' 
				|| peek=='/' || peek=='.' || peek==':'){
				c=getNext();
				addToLexText(c);
			    }else
				identloop=false;
			}
			loop=false;	
		    }else
			if (!Character.isWhitespace(c)){
			    m_LastLexeme=L_ERROR;
			    error("Expected ~ _ or alpha numeric character");
			    loop=false;	
			}
		}
	    }
	}
	return m_LastLexeme;
    }
    /**
     * This method parses a TreeNode from a text stream
     */
    TreeNode parseTreeNode(TreeNode parent) throws java.io.IOException{
	TreeNode node=new TreeNode();
	if (parent!=null)
	    parent.addChild(node);
	int lexeme=lex();
	switch(lexeme){
	case L_ERROR:
	    break;
	case L_IDENT:
	    node.label(m_LexText.toString());
	    //	    Tracer.trace("read node label "+node.label());
	    lexeme=lex();
	    switch (lexeme){
	    case L_ERROR:
		break;
	    case L_IDENT:
		unLex();
		break;
	    case L_TEXT:
		node.data(m_LexText.toString());
		lexeme=lex();
		switch (lexeme){
		case L_TEXT:
		case L_IDENT:
		    unLex();
		    break;
		case L_LBRACE:
		    parseTreeNodeList(node);
		    break;
		case L_RBRACE:
		    if (parent!=null)
			unLex();
		    else
			error("Unexpected right brace parsing text after label - no parent node");
		    break;
		}
		break;
	    case L_LBRACE:
		parseTreeNodeList(node);
		break;
	    case L_RBRACE:
		if (parent!=null)
		    unLex();
		else
		    error("Unexpected right brace parsing after label - no parent node");
		break;
	    }
	    break;
	case L_TEXT:
	    node.data(m_LexText.toString());
	    lexeme=lex();
	    switch (lexeme){
	    case L_ERROR:
		break;
	    case L_IDENT:
	    case L_TEXT:
		unLex();
		break;
	    case L_LBRACE:
		parseTreeNodeList(node);
		break;
	    case L_RBRACE:
		if (parent!=null)
		    unLex();
		else
		    error("Unexpected right brace parsing text with no label - no parent node");
		break;
	    }
	    break;
	case L_LBRACE:
	    parseTreeNodeList(node);
	    break;
	case L_RBRACE:
	    if (parent!=null)
		unLex();
	    else
		error("Unexpected right brace parsing sublist with no label or text - no parent node");
	    break;
	}
	return node;
    }
    /**
     * This method parses a list of TreeNodes from a text stream
     */
    void parseTreeNodeList(TreeNode node) throws java.io.IOException {
	boolean loop=true;
	do{
	    int lexeme=lex();
	    switch(lexeme){
	    case L_IDENT:
	    case L_TEXT:
	    case L_LBRACE:
		unLex();
		parseTreeNode(node);
		break;
	    case L_EOF:
		error("Expected right brace parsing sub-list");
	    case L_ERROR:
	    case L_RBRACE:
		loop=false;
	    }
	}while (loop);
    }
}
